#include "kernel/task/task.h"
#include "kernel/log/log.h"
#include "kernel/memory/memory.h"
#include "libs/msr.h"
#include "kernel/timer/time.h"
#include "kernel/memory/memory.h"
#include "kernel/vfs/fat32.h"
#include "kernel/syscall/user.h"
#include "libs/stdio.h"
#include "libs/sched.h"
#include "libs/fcntl.h"
#include "libs/errno.h"
extern "C" void testDisk(void);

task_union init_task_union __attribute__((__section__ (".data.init_task")));
extern  char _sinitdata;
extern  char _erodata;
extern uint64_t _stack_start;
TSS init_tss[MAX_CPU];

extern "C" void ret_system_call(void);
extern "C" void kernel_thread_func(void);


//=========================================temp functions==============




extern "C" void userFunction(void){





    char str[] = "abcd.txt";

    int fd = open(str,0);
    
    char* buffer = new char[4];
    memset(buffer,0,4);


    read(fd,(uint8_t*)buffer,3);
    user_level_printf(buffer);
    memset(buffer,0,4);
    memset(buffer,0x61,3);
    
    lseek(fd,0,SEEK_SET);

    write(fd,(uint8_t*)buffer,3);

    read(fd,(uint8_t*)buffer,3);
    user_level_printf(buffer);

    close(fd);

    while (1)
    {
        /* code */
        
    }
    
}









CpuSchedule::CpuSchedule(uint64_t taskCount,int64_t slice){

    taskRuningCount = taskCount;
    cpuExecTaskJiffies = slice;

}

CpuSchedule::~CpuSchedule(){



}

//=========================================taskmemory==============


taskmemory::taskmemory(/* args */)
{
}

taskmemory::~taskmemory()
{
}




//=========================================thread==============

thread::thread(uint64_t rsp0, uint64_t rip, uint64_t rsp, uint64_t fs, uint64_t gs, uint64_t cr2, uint64_t trap_nr,uint64_t error_code)
:rsp0(rsp0),	//in tss
    rip(rip),
    rsp(rsp),	
    fs(fs),
    gs(gs),
    cr2(cr2),
    trap_nr(trap_nr),
    error_code(error_code)
{
}

 thread::thread(){



 }

thread::~thread()
{
}






//=========================================task==============
task::task(/* args */)
{




}

task::~task()
{




}








taskmanager* taskmanager::taskm = 0;
taskmanager::taskmanager(/* args */)
{

    taskm = this;
    uint64_t cpuid = getAPICID();

    initIdleTask();
    currentTask[cpuid]->thread->rsp0 = (unsigned long)(_stack_start + 0x1000);
    currentTask[cpuid]->thread->rsp = (unsigned long)(_stack_start);
    initTaskSchedule();
    // LOG_INFO("address=%lx\n",&(init_thread));
    
    // LOG_INFO("init_task_union address = %#018lx\n",(uint64_t)init_task[0]);
    

}

void taskmanager::initIdleTask(){

    uint64_t cpuid = getAPICID();
    init_task[cpuid] = new task();
    currentTask[cpuid] = init_task[cpuid];
    currentTask[cpuid]->addr_limit = 0xffff800000000000;
    currentTask[cpuid]->priority = 2;
    currentTask[cpuid]->vrunTime = 0x700000000;
    currentTask[cpuid]->state = TASK_UNINTERRUPTIBLE;
    currentTask[cpuid]->flags = PF_KTHREAD;
    currentTask[cpuid]->pid = ++pidCount;
    currentTask[cpuid]->counter = 1;
    currentTask[cpuid]->signal = 0;
    currentTask[cpuid]->parent = currentTask[0];




    thread* trd = new thread();
    // LOG_INFO("address=%lx\n",&(init_thread));
    trd->fs = KERNEL_DS;
    trd->gs = KERNEL_DS;
    trd->cr2 = 0;
    trd->trap_nr = 0;
    trd->error_code = 0;
    trd->rip = 0;
    currentTask[cpuid]->thread = trd;

    init_mm = new taskmemory();
    init_mm->pgd = (pml4t_t *)Get_gdt();
    init_mm->end_code = MEMORY->globalMemoryDesc.end_code;
    init_mm->start_brk = MEMORY->globalMemoryDesc.start_brk;
    init_mm->end_brk = current->addr_limit;
    init_mm->start_data = (uint64_t)&_data;
    init_mm->end_data = MEMORY->globalMemoryDesc.end_data;
    init_mm->start_rodata = (uint64_t)_rodata;
    init_mm->end_rodata = (uint64_t)&_erodata;
    init_mm->end_rodata = MEMORY->globalMemoryDesc.end_rodata;
    init_mm->start_bss = _bss;
    init_mm->end_bss = _ebss;

    
    init_mm->start_stack = _stack_start;
    currentTask[cpuid]->mm =  init_mm;


    wrmsr(IA_32_SYSENTER_CS,KERNEL_CS);

    taskQueue.push(currentTask[cpuid]);

}

void taskmanager::initTaskSchedule(){

    uint64_t cpuid = getAPICID();
    schedules[cpuid] = (CpuSchedule*)new CpuSchedule(1,4);

}

taskmanager::~taskmanager()
{


}

void taskmanager::SetTSS64(unsigned long rsp0,unsigned long rsp1,unsigned long rsp2,unsigned long ist1,unsigned long ist2,unsigned long ist3,
unsigned long ist4,unsigned long ist5,unsigned long ist6,unsigned long ist7){

    uint64_t cpuid = getAPICID();

    init_tss[cpuid].rsp0 = rsp0;
    init_tss[cpuid].rsp1 = rsp1;
    init_tss[cpuid].rsp2 = rsp2;
    init_tss[cpuid].IST1 = ist1;
    init_tss[cpuid].IST2 = ist2;
    init_tss[cpuid].IST3 = ist3;
    init_tss[cpuid].IST4 = ist4;
    init_tss[cpuid].IST5 = ist5;
    init_tss[cpuid].IST6 = ist6;
    init_tss[cpuid].IST7 = ist7;

}

unsigned long taskmanager::do_fork(StackFrame * regs, unsigned long clone_flags, unsigned long stack_start, unsigned long stack_size)
{
	task *tsk = NULL;
	uint64_t ret = 0;
	// LOG_INFO("alloc_pages,bitmap:%#018lx\n",MEMORY->globalMemoryDesc.bits_map);


	tsk = (task*)MEMORY->kmalloc(STACK_SIZE,0);

    if(tsk == NULL){
        LOG_ERROR("no enough memory!\n");
        ret = -EAGAIN;
        goto alloc_copy_task_fail;

    }

    // LOG_INFO("new task address,bitmap:%#018lx\n",tsk);
    
	memset(tsk,0,sizeof(*tsk));
    memcpy(current,tsk,sizeof(task));

	tsk->pid = ++pidCount;
    tsk->priority = 2;
    tsk->state = TASK_UNINTERRUPTIBLE;	
    tsk->cpuId = getAPICID();
    tsk->preemptCount = 0;
    tsk->parent = current;
    tsk->flags = PF_KTHREAD;
    tsk->vrunTime = 0;


	memcpy(regs,(void *)((unsigned long)tsk + STACK_SIZE - sizeof(StackFrame)),sizeof(StackFrame));

    ret = -ENOMEM;

    if(copyFlags(clone_flags,tsk))
        goto copy_flags_fail;

    if(copyMm(clone_flags,tsk))
        goto copy_mm_fail;
   
    if(copyFiles(clone_flags,tsk))
        goto copy_files_fail;
    
    if(copyThread(clone_flags,stack_start,stack_size,tsk,regs))
        goto copy_thread_fail;

    ret = tsk->pid;
   
    wakeupProcess(tsk);

fork_ok:
    return ret;

copy_thread_fail:
    exitThread(tsk);

copy_files_fail:
    exitFiles(tsk);

copy_mm_fail:
    exitMm(tsk);

copy_flags_fail:
alloc_copy_task_fail:
    MEMORY->kfree(tsk);
	return ret;
}






int taskmanager::kernel_thread(uint64_t (* fn)(unsigned long), unsigned long arg, unsigned long flags)
{
	StackFrame regs;
	memset(&regs,0,sizeof(regs));

	regs.RBX = (unsigned long)fn;
	regs.RDX = (unsigned long)arg;

	regs.DS = KERNEL_DS;
	regs.ES = KERNEL_DS;
	regs.CS = KERNEL_CS;
	regs.OLDSS = KERNEL_DS;
	regs.RFLAGS = (1 << 9);
	regs.RIP = (uint64_t)kernel_thread_func;
	return do_fork(&regs,flags | CLONE_VM,0,0);
}


//保存 rsp0 和 
volatile void taskmanager::SwitchTo(task* prev, task* next){

    
	init_tss[getAPICID()].rsp0 = next->thread->rsp0;
    wrmsr(IA32_SYSENTER_ESP,next->thread->rsp0);
    TASKMANAGER->currentTask[getAPICID()] = next;
	__asm__ __volatile__("movq	%%fs,	%0 \n\t":"=a"(prev->thread->fs));
	__asm__ __volatile__("movq	%%gs,	%0 \n\t":"=a"(prev->thread->gs));

	__asm__ __volatile__("movq	%0,	%%fs \n\t"::"a"(next->thread->fs));
	__asm__ __volatile__("movq	%0,	%%gs \n\t"::"a"(next->thread->gs));
}



void taskmanager::taskInit(){

    uint64_t cpuid = getAPICID();
    task *p = NULL;
    uint64_t* tmp = NULL;

    uint64_t* vaddr = (uint64_t*)Phy_To_Virt((uint64_t)Get_gdt() & (~0xfffUL));
    LOG_ERROR("address:%lx\n",vaddr);
    *vaddr  = 0UL;

    for (size_t i = 256; i < 512; i++)
    {
        /* code */
        tmp = vaddr + i;
        if(*tmp == 0){

            uint64_t* virtualAddress = (uint64_t*)MEMORY->kmalloc(PAGE_4K_SIZE,0);
            memset(virtualAddress,0,PAGE_4K_SIZE);
            set_mpl4t(tmp,mk_mpl4t(Virt_To_Phy(virtualAddress),PAGE_KERNEL_GDT));

        }
    }
    
    
    init_tss[cpuid].rsp0 = current->thread->rsp0;

    list_init(&currentTask[cpuid]->list);
    
  
    kernel_thread(init,10,CLONE_FS | CLONE_SIGNAL );


    INTERRUPTMANAGER->taskManagerHaveFinish = 1;
   
    shcedule();
}


void taskmanager::shcedule(){


    uint64_t cpuid = getAPICID();
    task* tmpTask = NULL;
    current->flags &= ~NEED_SCHEDULE;

    tmpTask = getNextScheduleTask();

    // if(tmpTask->pid == 1)
    //     LOG_ERROR("come into schedule! RIP:%lx\n",tmpTask->thread->rip);

    if((current->vrunTime >= tmpTask->vrunTime || current->state != TASK_RUNNING)){

        if(current->state == TASK_RUNNING)
        {
           
            addTaskToScheduleQueue(current);
        }

        if(schedules[cpuid]->cpuExecTaskJiffies <= 0){
            switch (tmpTask->priority)
            {
            case 0/* constant-expression */:
                /* code */
            case 1:
                schedules[cpuid]->cpuExecTaskJiffies = 4 / schedules[cpuid]->taskRuningCount;
                break;

            case 2:
            default:
                schedules[cpuid]->cpuExecTaskJiffies = 4 / schedules[cpuid]->taskRuningCount * 3;
                break;
            }
         }

        
        Switch_Mm(current,tmpTask);
        Switch_To(current,tmpTask);

    
    }
    else{


        addTaskToScheduleQueue(tmpTask);

        if(schedules[cpuid]->cpuExecTaskJiffies <= 0){
            switch (tmpTask->priority)
            {
            case 0/* constant-expression */:
                /* code */
            case 1:
                schedules[cpuid]->cpuExecTaskJiffies = 4 / schedules[cpuid]->taskRuningCount;
                break;

            case 2:
            default:
                schedules[cpuid]->cpuExecTaskJiffies = 4 / schedules[cpuid]->taskRuningCount * 3;
                break;
            }
        }


    }

    
}






task* taskmanager::getNextScheduleTask(){


    class task* tsk = NULL;
    uint64_t cpuid = getAPICID();
    if(schedules[cpuid]->taskQueue.isEmpty()){
        //返回初始进程
        return init_task[cpuid];
    }
    schedules[cpuid]->taskRuningCount--;

    tsk = schedules[cpuid]->taskQueue.pop();
    
    return tsk;

}

int compareTo(task* temp,task* next){

    return temp->vrunTime < temp->vrunTime;

}

void taskmanager::addTaskToScheduleQueue(task* node){

    uint64_t cpuid = getAPICID();
    schedules[cpuid]->taskQueue.push(node,compareTo);
    schedules[cpuid]->taskRuningCount++;
}


int serachTo(task* tsk,task* t){

    return tsk->pid = t->pid;
    

}

task* taskmanager::getTaskByPid(uint64_t pid){

    task t;
    t.pid = pid;
    task* tsk =  taskQueue.get(&t,serachTo);
    return tsk;
    
 }
 
 inline void taskmanager::wakeupProcess(task* tsk)
 {
    tsk->state = TASK_RUNNING;
    addTaskToScheduleQueue(tsk);
    
    current->flags |= NEED_SCHEDULE;
    shcedule();
    
 }
 
 uint64_t taskmanager::copyFlags(uint64_t cloneFlags,task* tsk)
 {
    if(cloneFlags & CLONE_VM)
        tsk->flags |= PF_VFORK;
    return 0;
 }
 
 uint64_t taskmanager::copyFiles(uint64_t cloneFlags,task* tsk)
 {
        int error = 0;
        int i = 0;
        if(cloneFlags & CLONE_FS)
            goto out;
        for (size_t i = 0; i < TASK_FILE_MAX; i++)
        {
            /* code */
            if(current->fileHandle[i] != NULL){
                tsk->fileHandle[i] = new File();
                memcpy(current->fileHandle[i],tsk->fileHandle[i],sizeof(File));
            }
        }
        
out:
    return error;

 }
 
 uint64_t taskmanager::copyMm(uint64_t cloneFlags,task* tsk)
 {
     
    int error = 0;
    taskmemory* newmm = NULL;
    uint64_t * tmp;
    Page * p = NULL;
    uint64_t addr = 0x800000;
    uint64_t codeStartAddr = 0x800000;
    uint64_t stackStartAddr = 0xA00000;
    uint64_t brkStartAddr = 0xC00000;
    uint64_t* tmpTemp = 0;
    uint64_t* Global_CR3 = Get_gdt();
    uint64_t * virtualAddrss = NULL;

    if(cloneFlags & CLONE_VM)
        newmm = current->mm;
        goto out;

    newmm = new taskmemory();
    newmm->pgd = (pml4t_t*)Virt_To_Phy(MEMORY->kmalloc(PAGE_4K_SIZE,0));

    memcpy(Phy_To_Virt(init_task[getAPICID()]->mm->pgd) + 256,Phy_To_Virt(newmm->pgd) + 256,PAGE_4K_SIZE / 2);

    memset(Phy_To_Virt(newmm->pgd),0,PAGE_4K_SIZE);



	tmp = Phy_To_Virt((uint64_t *)((uint64_t)newmm->pgd & (~ 0xfffUL)) + ((addr >> PAGE_GDT_SHIFT) & 0x1ff));
	virtualAddrss = (uint64_t*)MEMORY->kmalloc(PAGE_4K_SIZE,0);
	set_mpl4t(tmp,mk_mpl4t(Virt_To_Phy(virtualAddrss),PAGE_USER_GDT));


	tmp = Phy_To_Virt((uint64_t *)(*tmp & (~ 0xfffUL)) + ((addr >> PAGE_1G_SHIFT) & 0x1ff));
	virtualAddrss = (uint64_t*)MEMORY->kmalloc(PAGE_4K_SIZE,0);
	set_pdpt(tmp,mk_pdpt(Virt_To_Phy(virtualAddrss),PAGE_USER_Dir));

    tmpTemp = tmp;

	tmp = Phy_To_Virt((uint64_t *)(*tmp & (~ 0xfffUL)) + ((addr >> PAGE_2M_SHIFT) & 0x1ff));
	p = MEMORY->alloc_pages(ZONE_NORMAL,1,PG_PTable_Maped);
	set_pdt(tmp,mk_pdt(p->PHY_address,PAGE_USER_Page));
    
    tmp = Phy_To_Virt((uint64_t *)(*tmpTemp & (~ 0xfffUL)) + ((stackStartAddr >> PAGE_2M_SHIFT) & 0x1ff));
	p = MEMORY->alloc_pages(ZONE_NORMAL,1,PG_PTable_Maped);
	set_pdt(tmp,mk_pdt(p->PHY_address,PAGE_USER_Page));


    memcpy((void*)codeStartAddr,Phy_To_Virt(p->PHY_address),stackStartAddr - codeStartAddr);

    if(current->mm->end_brk - current->mm->start_brk != 0)
    {
        tmp = Phy_To_Virt((uint64_t *)(*tmpTemp & (~ 0xfffUL)) + ((brkStartAddr >> PAGE_2M_SHIFT) & 0x1ff));
        p = MEMORY->alloc_pages(ZONE_NORMAL,1,PG_PTable_Maped);
        set_pdt(tmp,mk_pdt(p->PHY_address,PAGE_USER_Page));
        memcpy((void *)brkStartAddr,Phy_To_Virt(p->PHY_address),PAGE_2M_SIZE);
    }





out:
    tsk->mm = newmm;
    return error;

 }
 


 uint64_t taskmanager::copyThread(uint64_t cloneFlags,uint64_t stackStart,uint64_t stackSize,task* tsk,StackFrame* regs)
 {
    thread* thd = NULL;
    StackFrame* childregs = NULL;
    thd = (thread*)(tsk + 1);
    memset(thd,0,sizeof(thread));

    childregs = (StackFrame*)((uint64_t)tsk + STACK_SIZE)  - 1;

    memcpy(regs,childregs,sizeof(StackFrame));
    childregs->RAX = 0;
    childregs->OLDRSP = stackStart;

    thd->rsp0 = (uint64_t)tsk + STACK_SIZE;
    thd->rsp = (uint64_t)childregs - 0x100;
    memcpy(regs,(void*)thd->rsp,STACK_SIZE);
    // LOG_ERROR("childregs:%lx\n",childregs);
    // LOG_ERROR("tsk:%lx\n",tsk);
    // LOG_ERROR("rsp0:%lx\n",thd->rsp0);
    // LOG_ERROR("rsp:%lx\n",thd->rsp);
    thd->fs = current->thread->fs;
    thd->gs = current->thread->gs;
    

    if(tsk->flags & PF_KTHREAD)
        thd->rip = (uint64_t)kernel_thread_func;
    else
        thd->rip = (uint64_t)ret_system_call;

    tsk->thread = thd;
    return 0;
 }
 




 void taskmanager::exitFiles(task* tsk)
 {
     if(tsk->flags & PF_VFORK)
        ;
    else
        for (size_t i = 0; i < TASK_FILE_MAX; i++)
        {
            /* code */
            if(tsk->fileHandle[i] != NULL)
            {
                delete tsk->fileHandle[i];
            }

        }

    memset(tsk->fileHandle,0,sizeof(File*) * TASK_FILE_MAX);
        
 }
 

 
 void taskmanager::exitMm(task* tsk)
 {

    uint64_t addr = 0x800000;
    uint64_t* tmp1 = 0;
    uint64_t* tmp2 = 0;
    uint64_t* tmp3 = 0;
    uint64_t* tmp4 = 0;

    if(tsk->flags & PF_VFORK)
        return;
    if(tsk->mm->pgd != NULL){

    tmp1 = Phy_To_Virt((uint64_t *)((uint64_t)tsk->mm->pgd & (~ 0xfffUL)) + ((addr >> PAGE_GDT_SHIFT) & 0x1ff));


	tmp2 = Phy_To_Virt((uint64_t *)(*tmp1 & (~ 0xfffUL)) + ((addr >> PAGE_1G_SHIFT) & 0x1ff));


	tmp3 = Phy_To_Virt((uint64_t *)(*tmp2 & (~ 0xfffUL)) + ((addr >> PAGE_2M_SHIFT) & 0x1ff));
    
    addr = 0xa00000;
	tmp4 = Phy_To_Virt((uint64_t *)(*tmp2 & (~ 0xfffUL)) + ((addr >> PAGE_2M_SHIFT) & 0x1ff));
 
    MEMORY->free_pages(Phy_to_2M_Page(*tmp4),1);
    MEMORY->free_pages(Phy_to_2M_Page(*tmp3),1);
    MEMORY->kfree(Phy_To_Virt(*tmp2));
    MEMORY->kfree(Phy_To_Virt(*tmp1));
    MEMORY->kfree(Phy_To_Virt(tsk->mm->pgd));


    }

    if(tsk->mm !=NULL)
        delete tsk->mm;

 }
 
 void taskmanager::exitThread(task* tsk)
 {
     
 }
 
 File* taskmanager::openExecFile(char* path)
 {
        DirEntry* dentry = NULL;
        File* filp  = NULL;
        dentry = FAT32FILESYSTEM->pathSearch(path,0);
        if(dentry == NULL)
        {

            LOG_ERROR("find dentry error!\n");

            return NULL;

        }

        if(dentry->dirInode->attribute == FS_ATTR_DIR)
        {

            LOG_ERROR("find file attribute error!\n");

            return NULL;

        }

        filp = new FAT32File(); 
        if(filp == NULL)
        {
            LOG_ERROR("find dile error!\n");
            return NULL;
        }
            

        filp->position = 0;
        filp->mode = 0;
        filp->dentry = dentry;
        filp->mode = O_RDONLY;
        return filp;
 }